import numpy as np
import sys
sys.path.append("../") # 将上一级目录也添加到模块的搜索目录中
from common.metrics import accuracy_score


# 我自己的实现，只用了numpy库完成kNN算法
# 模仿skilearn库中的knn算法的封装方法，改造该方法
class KNNClassifier2_1:
    def __init__(self, k):
        """初始化kNN分类"""
        assert k >= 1, "k值必须大于等于0"
        self.k = k
        self._X_train = None
        self._y_train = None

    def fit(self, X_train, y_train):
        """根据训练数据集的X_train和y_train训练kNN分类器"""
        assert self.k <= X_train.shape[0], "k值必须小于等于训练样本个数"
        assert X_train.shape[0] == y_train.shape[0], "训练样本个数必须和类型数相等"
        self._X_train = X_train
        self._y_train = y_train
        return self

    def predict(self, X_predict):
        assert self._X_train.shape[1] == X_predict.shape[1], "待预测的样本特征数必须和训练样本的特征数相等"
        y_predict = [self._predict(x) for x in X_predict]

        return np.array(y_predict)

    def _predict(self, x):
        # 计算样本a到每一个训练样本的距离
        dis = [np.sqrt(np.sum((x - x_train) ** 2)) for x_train in self._X_train]
        distances = np.array(dis)

        # 找出离a最近的k个样本点的行索引
        nearestDisIndexes = distances.argsort()[:self.k]

        # 统计最近的k个样本中结果为0和1的个数
        # 统计训练样本类型中的所有的样本类型
        y_type = np.unique(self._y_train)
        # 统计每一种类型出现的次数
        counts = [np.sum(self._y_train[nearestDisIndexes] == i) for i in y_type]

        # 返回 距离样本a最近的 k个样本中 出现次数最多的类型 作为KNN算法预测的结果
        return y_type[np.argmax(counts)]

    def score(self, X_test, y_test):
        """根据测试数据集 X_test 和 y_test 确定当前模型的准确度"""
        y_predict = self.predict(X_test)
        return accuracy_score(y_test, y_predict)

